--
-- Ret/prot paladin rotation helper (only for ret now)
--

-- '_G' stuff is to keep lualint happy

if _G['select'](2, _G['UnitClass']('player')) ~= 'PALADIN' then return end

-------------------------------------------
-- Local references to globals for speed and lint's happiness
-------------------------------------------
local _G = _G

local UnitHealth = _G['UnitHealth']
local UnitHealthMax = _G['UnitHealthMax']
local strsplit = _G['strsplit']
local max = _G['max']
local min = _G['min']
local GetTime = _G['GetTime']
local UnitBuff = _G['UnitBuff']
local IsSpellKnown = _G['IsSpellKnown']

local Rotor = _G['Rotor']

----------------------------------------
-- The
----------------------------------------
local MODULE = 'Paladin'

-------------------------------------------
-- Constants
-------------------------------------------

--
-- Spell ids
--
local JUDG_ID   = 20271     -- judgement
local TV_ID     = 85256     -- templar's verdict
local CS_ID     = 35395     -- crusader strike
local EXO_ID    = 879       -- exorcism
local BOK_ID    = 20217     -- blessing of the kings
local BOM_ID    = 19740     -- blessing of the might
local AS_ID     = 31935     -- avenger's shield
local SotR_ID   = 53600     -- shield of the righteous
local AoW_ID    = 53486     -- the art of war
local HoW_ID    = 24275     -- hammer of wrath
local INQ_ID    = 84963     -- Inquisition
local HotR_ID   = 53595     -- hammer of the righteous

local CS_NAME = Rotor.spell_name_cache[CS_ID]
local HotR_NAME = Rotor.spell_name_cache[HotR_ID]

-- seals
local SoR_ID = 20154
local SoI_ID = 20165
local SoJ_ID = 20164
local SoT_ID = 31801
-- seal names
local SEAL_NAMES = {
    Rotor.spell_name_cache[SoT_ID],
    Rotor.spell_name_cache[SoI_ID],
    Rotor.spell_name_cache[SoR_ID],
    Rotor.spell_name_cache[SoJ_ID],
}
-- buff names
local BOK_NAME = Rotor.spell_name_cache[BOK_ID]
local BOM_NAME = Rotor.spell_name_cache[BOM_ID]
local MotW_NAME = Rotor.spell_name_cache[1126] -- mark of the wild

-- auras
local DEV_AURA_ID = 465
local RET_AURA_ID = 7294
local CONC_AURA_ID = 19746
local COMBAT_AURA_NAMES = {
    Rotor.spell_name_cache[DEV_AURA_ID],
    Rotor.spell_name_cache[RET_AURA_ID],
    Rotor.spell_name_cache[CONC_AURA_ID]
}

-- The module
local Mod = Rotor:NewModule(MODULE)

local IsRet = false
local IsProt = false

-- Spell objects
local common_spell_defs = {
    judg = {id = JUDG_ID},
    cs = {id = CS_ID},
    exo = {id = EXO_ID},
    how = {id = HoW_ID, must_exists = true},
    inq = {id = INQ_ID, must_exists = true, with_buff = true},
}
local common_aura_defs = {
}

local ret_spell_defs = {
    tv = {id = TV_ID},
    zeal = {id = 85696, must_exists = true},
}
local ret_aura_defs = {
    aow = {id = AoW_ID},
    divpurp = {id = 86172},
}

local prot_spell_defs = {
    as = {id = AS_ID},
    sotr = {id = SotR_ID, must_exists = true},
}
local prot_aura_defs = {
    gcrus = {id = 85416},
}

local spells
local buffs
local debuffs

-- Local vars
local now = 0
local gcd = 0
local extra_hp = 0
local dp_consumed = false
local aow_consumed = false

--
-- Methods implementation
--
function Mod:Init(opts)
    IsProt = Rotor.spec == 2
    IsRet = Rotor.spec == 3

    spells, buffs, debuffs = Rotor:RegisterSpellsAndAuras(common_spell_defs, common_aura_defs)
    if IsRet then
        Rotor:RegisterSpellsAndAuras(ret_spell_defs, ret_aura_defs, spells, buffs, debuffs)
    else
        Rotor:RegisterSpellsAndAuras(prot_spell_defs, prot_aura_defs, spells, buffs, debuffs)
    end

    Rotor:showUpperText()
    --Rotor:showPowerDisplay()

    local spec_name
    if IsRet then
        spec_name = 'Ret'
    else
        spec_name = 'Prot'
    end
    Rotor:print('Paladin module initialized ('..spec_name..')')

    return true
end

function Mod:Done()
    spells = nil
    buffs = nil
    debuffs = nil
end

function Mod:CastSent(spell)
    if (spell == CS_NAME) or (spell == HotR_NAME) then
        extra_hp = 1
    elseif spell == spells.exo.name then
        aow_consumed = true
    end
end

function Mod:CastFinished(spell, succ, fail_reason)
    if (spell == CS_NAME) or (spell == HotR_NAME) then
        extra_hp = 0
    elseif spell == spells.exo.name then
        aow_consumed = false
    end
end

local function CheckUnsent(spell, time_since_last_sent)
    return spell and (spell.sentAt <= spell.doneAt) and (spell.doneAt + (time_since_last_sent or 0.7) < now)
end

local function CheckOffCdAndUnsent(spell, time_since_last_sent, wth)
    return spell and (spell.cd - (wth or 0) <= gcd) and CheckUnsent(spell, time_since_last_sent)
end

local function AnySealUp()
    for _, seal_name in ipairs(SEAL_NAMES) do
        if UnitBuff('player', seal_name) then
            return true
        end
    end
    return false
end

local function IsCombatAuraUp()
    for _, aura_name in ipairs(COMBAT_AURA_NAMES) do
        if UnitBuff('player', aura_name) then
            return true
        end
    end
    return false
end

function Mod:NextSkill(gcd_arg, cur_spell)
    gcd = gcd_arg + 0.3
    now = GetTime()

    if not AnySealUp() then
        return SoT_ID
    end

    if not IsCombatAuraUp() then
        if IsProt then
            return DEV_AURA_ID
        else
            return RET_AURA_ID
        end
    end

    if not UnitBuff('player', BOK_NAME)
        and not UnitBuff('player', MotW_NAME)
        and IsSpellKnown(BOK_ID) then
        return BOK_ID
    elseif not UnitBuff('player', BOM_NAME) and IsSpellKnown(BOM_ID) then
        return BOM_ID
    end

    if IsProt then
        return Mod:NextProtSkill(gcd_arg, cur_spell)
    else
        return Mod:NextRetSkill(gcd_arg, cur_spell)
    end
end

local function CheckHoW()
    return spells.how and CheckOffCdAndUnsent(spells.how) and ((UnitHealth('target') / UnitHealthMax('target')) <= 0.2)
end

local function CheckInq()
    if not spells.inq then
        return false
    else
        return (buffs.inq.time_left < 3) and ((Rotor.combo_points > 0) or (buffs.divpurp.time_left > gcd)) and CheckUnsent(spells.inq)
    end
end

function Mod:NextRetSkill(gcd_arg, cur_spell)
    local spell_id = 0
    local hp = min(Rotor.combo_points + extra_hp, 3)

    if CheckInq() then
        spell_id = INQ_ID
    elseif ((hp == 3) or (buffs.divpurp.time_left > gcd_arg)) and spells.zeal and CheckOffCdAndUnsent(spells.zeal) then
        spell_id = spells.zeal.id
    elseif ((hp == 3) or (buffs.divpurp.time_left > gcd_arg)) and CheckUnsent(spells.tv) then
        spell_id = TV_ID
    elseif CheckOffCdAndUnsent(spells.cs) then
        spell_id = CS_ID
    elseif CheckHoW() then
        spell_id = HoW_ID
    elseif buffs.aow and (buffs.aow.time_left > gcd_arg) and not aow_consumed then
        spell_id = EXO_ID
    end

    -- note: use j if currently recomended spell is oor
    if ((spell_id == 0) or not Rotor:CheckInRange(spell_id)) and CheckOffCdAndUnsent(spells.judg) then
        spell_id = JUDG_ID
    end

    return spell_id
end

function Mod:NextProtSkill(gcd_arg, cur_spell)
    local spell_id = 0

    if not Rotor.in_combat and CheckOffCdAndUnsent(spells.as) then
        return AS_ID
    end

    if spells.sotr and (Rotor.combo_points == 3) and CheckUnsent(spells.sotr) then
        spell_id = SotR_ID
    elseif CheckOffCdAndUnsent(spells.cs) then
        spell_id = CS_ID
    elseif CheckOffCdAndUnsent(spells.as) then
        spell_id = AS_ID
    elseif CheckHoW() then
        spell_id = HoW_ID
    end

    if ((spell_id == 0) or not Rotor:CheckInRange(spell_id)) and CheckOffCdAndUnsent(spells.judg) then
        spell_id = JUDG_ID
    end

    return spell_id
end
----------------------------------------
function Mod:TargetChanged(old_target)
    extra_hp = 0
    dp_consumed = false
    aow_consumed = false
end

-- Command line handler
function Mod:HandleCommandLine(cmd)
    local cmd, args = strsplit(' ', (cmd or ''):lower(), 2)
    -- handle command line here
end

-- Module detection
function Mod:Detect()
    return (Rotor.spec == 3) or (Rotor.spec == 2)
end
